package com.yunli.mall.common.util;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.yunli.mall.common.exception.MallRuntimeException;
import com.yunli.mall.common.exception.ErrorCode;
import com.yunli.mall.common.schema.BaseRequest;
import org.apache.http.Header;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.NoHttpResponseException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.HttpRequestRetryHandler;
import org.apache.http.client.ServiceUnavailableRetryStrategy;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.HttpHostConnectException;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.protocol.HttpContext;
import org.apache.http.util.EntityUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * @author wangyue
 * @description
 * @date 2020-01-02 15:15
 */
public class HttpRequestUtil {

    /**
     * 重试次数
     */
    private static final int RETRY_COUNT = 3;

    /**
     * 重试间隔
     */
    private static final int RETRY_INTERVAL = 100;

    private static final RequestConfig requestConfig;
    private static final HttpClient httpClient;

    static {
        httpClient = HttpClientBuilder.create().disableAutomaticRetries().build();
        requestConfig = RequestConfig.custom().setConnectTimeout(50000).setSocketTimeout(50000).setConnectionRequestTimeout(50000).build();
    }

    /**
     * 拼接url参数
     *
     * @param url
     * @param key
     * @param value
     * @return
     */
    public static String addParam(String url, String key, String value) {
        if (StringUtils.hasText(url) && StringUtils.hasText(key)) {
            String symbol = url.contains("?") ? "&" : "?";
            return url.concat(symbol).concat(key).concat("=").concat(StringUtils.hasText(value) ? value : "");
        }
        throw new MallRuntimeException(ErrorCode.PARAM_ERROR);
    }

    /**
     * @param url
     * @param baseRequest
     * @return
     */
    public static String execPost(String url, BaseRequest baseRequest) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeaders(generateHeader());
        httpPost.setConfig(requestConfig);
        ObjectMapper objectMapper = new ObjectMapper();
        if (Objects.nonNull(baseRequest)) {
            try {
                StringEntity entity = new StringEntity(objectMapper.writeValueAsString(baseRequest), StandardCharsets.UTF_8);
                httpPost.setEntity(entity);
            } catch (Exception e) {
                throw new MallRuntimeException(e);
            }
        }
        return doPutOrPost(httpPost);
    }

    /**
     * @param url
     * @param param
     * @return
     */
    public static String execPost(String url, Map<String, String> param) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeaders(generateHeader());
        httpPost.setConfig(requestConfig);
        if (null != param && !param.isEmpty()) {
            List<NameValuePair> nameValuePairList = new ArrayList<>();
            for (Map.Entry<String, String> entry : param.entrySet()) {
                nameValuePairList.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
            StringEntity stringEntity = new UrlEncodedFormEntity(nameValuePairList, StandardCharsets.UTF_8);
            httpPost.setEntity(stringEntity);
        }
        return doPutOrPost(httpPost);
    }

    /**
     * 请求类型为raw
     *
     * @param url
     * @param jsonStr
     * @return
     */
    public static String execPostRaw(String url, String jsonStr) {
        HttpPost httpPost = new HttpPost(url);
        httpPost.setHeaders(generateHeader());
        httpPost.setConfig(requestConfig);
        StringEntity stringEntity = null;
        try {
            stringEntity = new StringEntity(jsonStr);
        } catch (UnsupportedEncodingException e) {
            throw new MallRuntimeException(ErrorCode.PARAM_ERROR);
        }
        stringEntity.setContentType(ContentType.APPLICATION_JSON.toString());
        httpPost.setEntity(stringEntity);
        return doPutOrPost(httpPost);
    }

    /**
     * 请求类型为put
     *
     * @param url
     * @param jsonStr
     * @return
     */
    public static String execPutRaw(String url, String jsonStr) {
        HttpPut httpPut = new HttpPut(url);
        httpPut.setHeaders(generateHeader());
        httpPut.setConfig(requestConfig);
        StringEntity stringEntity = null;
        try {
            stringEntity = new StringEntity(jsonStr);
        } catch (UnsupportedEncodingException e) {
            throw new MallRuntimeException(ErrorCode.PARAM_ERROR);
        }
        stringEntity.setContentType(ContentType.APPLICATION_JSON.toString());
        httpPut.setEntity(stringEntity);
        return doPutOrPost(httpPut);
    }


    /**
     * @param httpPutOrPost
     * @return
     */
    private static String doPutOrPost(HttpEntityEnclosingRequestBase httpPutOrPost) {
        try {
            HttpResponse response = httpClient.execute(httpPutOrPost);
            return EntityUtils.toString(response.getEntity());
        } catch (ConnectTimeoutException e) {
            throw new MallRuntimeException(ErrorCode.HTTP_REQUEST_TIME_OUT);
        } catch (HttpHostConnectException e) {
            throw new MallRuntimeException(ErrorCode.SERVICE_UNAVAILABLE);
        } catch (IOException e) {
            throw new MallRuntimeException(e);
        }
    }

    /**
     * 请求类型为delete
     *
     * @param url
     * @param jsonStr
     * @return
     */
    public static String execDeleteRaw(String url, String jsonStr) {
        HttpDelete httpDelete = new HttpDelete(url);
        httpDelete.setHeaders(generateHeader());
        httpDelete.setConfig(requestConfig);
        return doDelete(httpDelete);
    }


    /**
     * @param httpDelete
     * @return
     */
    private static String doDelete(HttpDelete httpDelete) {
        try {
            HttpResponse response = httpClient.execute(httpDelete);
            return EntityUtils.toString(response.getEntity());
        } catch (ConnectTimeoutException e) {
            throw new MallRuntimeException(ErrorCode.HTTP_REQUEST_TIME_OUT);
        } catch (HttpHostConnectException e) {
            throw new MallRuntimeException(ErrorCode.SERVICE_UNAVAILABLE);
        } catch (IOException e) {
            throw new MallRuntimeException(e);
        }
    }

    /**
     * @param url
     * @return
     */
    public static String execGet(String url) {
        HttpGet httpGet = null;
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            URI uri = uriBuilder.build();
            httpGet = new HttpGet(uri);
        } catch (URISyntaxException e) {
            throw new MallRuntimeException(e);
        }
        return doGet(httpGet);
    }

    /**
     * @param url
     * @param baseRequest
     */
    public static String execGet(String url, BaseRequest baseRequest) {
        HttpGet httpGet = null;
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            List<NameValuePair> nameValuePairList = new ArrayList<>();
            if (Objects.nonNull(baseRequest)) {
                for (Field field : baseRequest.getClass().getDeclaredFields()) {
                    field.setAccessible(true);
                    Object objValue = field.get(baseRequest);
                    if (null != objValue) {
                        nameValuePairList.add(new BasicNameValuePair(field.getName(), String.valueOf(objValue)));
                    }
                }
                uriBuilder.addParameters(nameValuePairList);
            }
            URI uri = uriBuilder.build();
            httpGet = new HttpGet(uri);
        } catch (URISyntaxException | IllegalAccessException e) {
            throw new MallRuntimeException(e);
        }
        return doGet(httpGet);
    }

    /**
     * @param url
     * @param param
     * @return
     */
    public static String execGet(String url, Map<String, String> param) {
        //参数
        List<NameValuePair> params = new ArrayList<>();
        if (param != null && !param.isEmpty()) {
            for (Map.Entry<String, String> entry : param.entrySet()) {
                params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
            }
        }
        HttpGet httpGet = null;
        try {
            URIBuilder uriBuilder = new URIBuilder(url);
            URI uri = uriBuilder.addParameters(params).build();
            httpGet = new HttpGet(uri);
        } catch (URISyntaxException e) {
            throw new MallRuntimeException(e);
        }
        return doGet(httpGet);
    }

    /**
     * @param httpGet
     * @return
     */
    private static String doGet(HttpGet httpGet) {
        try {
            HttpClient httpClient = HttpClientBuilder.create()
                    .setRetryHandler(getHttpRequestRetryHandler())
                    .setServiceUnavailableRetryStrategy(getRetryStrategy())
                    .build();
            httpGet.setConfig(requestConfig);
            httpGet.setHeaders(generateHeader());
            HttpResponse response = httpClient.execute(httpGet);
            return EntityUtils.toString(response.getEntity());
        } catch (ConnectTimeoutException e) {
            throw new MallRuntimeException(ErrorCode.HTTP_REQUEST_TIME_OUT);
        } catch (HttpHostConnectException e) {
            throw new MallRuntimeException(ErrorCode.SERVICE_UNAVAILABLE);
        } catch (IOException e) {
            throw new MallRuntimeException(e);
        }
    }

    /**
     * @return
     */
    private static HttpRequestRetryHandler getHttpRequestRetryHandler() {
        return (e, i, httpContext) -> {
            if (i > RETRY_COUNT) {
                return false;
            }
            return e instanceof UnknownHostException || e instanceof ConnectTimeoutException || e instanceof NoHttpResponseException;
        };
    }

    /**
     * @return
     */
    private static ServiceUnavailableRetryStrategy getRetryStrategy() {
        return new ServiceUnavailableRetryStrategy() {
            @Override
            public boolean retryRequest(HttpResponse httpResponse, int i, HttpContext httpContext) {
                return i <= RETRY_COUNT && httpResponse.getStatusLine().getStatusCode() != HttpStatus.SC_OK;
            }

            @Override
            public long getRetryInterval() {
                return RETRY_INTERVAL;
            }
        };
    }

    /**
     * @return
     */
    private static Header[] generateHeader() {
        return new Header[]{
                new BasicHeader(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8).toString()),
//                new BasicHeader(HttpHeaders.USER_AGENT, "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.89 Safari/537.36")
        };
    }

}
